// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using CIP4.Common;
using CIP4.Item;

namespace CIP4.Serializer
{
    public class XmlSerializerBuilderType : SerializerBuilderBaseType
    {
        // {0} - namespace
        // {1} - SetProperties invocations
        private static string CSharpSerializerBaseTypeLiteral =
            @"
    public class {0}_SerializerBaseType : SerializerBaseType
    {{
        public static object DeSerializeFromXElement(string typeName, XElement element)
        {{
            switch (typeName)
            {{
{1}
                default:
                    return ValueFromString(typeName, element.Value);
            }}
        }}

        public static void SerializeToXml(string typeName, object value, string keyValue, XmlTextWriter xwrtr)
        {{
            switch (typeName)
            {{
{2}
                default:
                    XmlValueFromObject(typeName, value, keyValue, xwrtr);
                    break;
            }}
        }}
    }}

";
        
        public static string GetSerializerBaseType()
        {
            return string.Format(CSharpSerializerBaseTypeLiteral,
                FirstNamespaceName, 
                DeSerializerBaseTypeBody,
                SerializerBaseTypeBody);
        }

        // {0} - qualified type name
        private static string CSharpDeSerializerBaseTypeBodyCaseLiteral =
            @"
                case ""{0}"":
                    return {1}.SetProperties(DateTime.Now, null, element, null, null);
";

        // {0} - qualified type name
        private static string CSharpSerializerBaseTypeBodyCaseLiteral =
            @"
                case ""{0}"":
                    {1}.Serialize(({2})value, keyValue, xwrtr, null, null, null, false);
                    break;
";

        public static void SerializerBaseTypeBodyCase(TypeContextType theType)
        {
            DeSerializerBaseTypeBody += string.Format(CSharpDeSerializerBaseTypeBodyCaseLiteral,
                theType.TheTypeName,
                theType.TypeSerializerName);
            SerializerBaseTypeBody += string.Format(CSharpSerializerBaseTypeBodyCaseLiteral,
                theType.TheTypeName,
                theType.TypeSerializerName,
                theType.TheTypeFullName);
        }

        public static string FirstNamespaceName { get; set; }

        public static string DeSerializerBaseTypeBody { get; set; }

        public static string SerializerBaseTypeBody { get; set; }

        public XmlSerializerBuilderType(TypeContextType aType)
        {
            TheType = aType;
            UsedTypes = new Dictionary<string, TypeContextType>();
        }

        public string GetSerializer(Dictionary<string, TypeContextType> alreadyDone)
        {
            try
            {
                if (alreadyDone.ContainsKey(TheType.TheTypeFullName)  || TheType.TheTypeName.EndsWith("<>"))
                    return string.Empty;
                Globals.AddAssemblyPath(Path.GetDirectoryName(TheType.TheType.Assembly.Location), Globals.DefaultLogger);
                return SerializeHeader(alreadyDone);
            }
            finally
            {
                Globals.RemoveAssemblyPath(Path.GetDirectoryName(TheType.TheType.Assembly.Location));
            }
        }

        private const string CSharpNewItemLiteral =
            // {1} - deserialize property assignment list
            // {0} - Type full name (namespace.type)
            // {2} - set private properties (horrible, uses reflection, makes a mockery of encapsulation etc.)
            @"
            {0} objectValue = new {0}();
{1} 
{2};
";

        private const string CSharpHeaderItemLiteral =
            // {0} - Type name
            // {1} - deserialize property assignment list
            // {2} - serialize property assignment list
            // {3} - PublishedID asignment
            // {4} - ExternalID assignment 
            // {5} - Type full name (namespace.type)
            // {6} - Deserialize subtypes code (works for items and values)
            // {7} - Serialize subtypes for items
            // {8} - serialize subtypes for values
            // {9} - set private properties (horrible, uses reflection, makes a mockery of encapsulation etc.)
            // (10)- stream handling for deserialize
            // {11} - newItem text - if the class is abstract, defines objectValue as "object objectValue = null;"
            // {12} - serializer name - either foo_Serializer or foo_Serializer<aType> (if it's generic)
            // {13} - type specific (as opposed to generic) name - the type name without the stuff in <> 
            // {14} - externalid functions
            // {15} - GetValue Function- this is getting out of hand!
            // {16} - key list - this is really getting out of hand...
            // (17) - CheckVersion function
            // {18} - equalitycomparer
            // {19} - version hndling
            // {20} - versionItem
            // {21} - GetItemVersionBody
            // {22} - first namespace name
@"
    public class {12} : {22}_SerializerBaseType
    {{
{17}
{16}
{15}
{14}
        public static {5} DeSerialize(DateTime versionTimestamp, object itemData, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath)
        {{
            return DeSerialize(versionTimestamp, itemData, StreamSeeds, propertyPath, null);
        }}
        public static {5} DeSerialize(DateTime versionTimestamp, object itemData, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, {5} defaultValue)
        {{
            XElement itemElement = GetXmlData(itemData);
            XElement element = null;
            if (itemElement.Name == XName.Get(""Item"", Globals.ciiNS))
            {{
                element = itemElement.LastElement();
                propertyPath = ""{5}"";
            }}
            else
                element = itemElement;


{6}

            {5} objectValue = SetProperties(versionTimestamp, itemElement, element, StreamSeeds, propertyPath);
    
{10}
{19}
            return objectValue == null? defaultValue : ({5})objectValue;
        }}

        public static {5} SetProperties(DateTime versionTimestamp, XElement itemElement, XElement element, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath)
        {{

{11}
            return ({5})objectValue;
        }}

        public static {5} GetItemVersion(long versionTimestamp, {5} previousVersion)
        {{
{21}
        }}

        public static {5} GetItemVersion(long versionTimestamp, {5}  previousVersion, Dictionary<string, object> Values)
        {{
{20}
            return ({5})objectValue;
        }}


        internal static List<{5}> DeSerializeList(DateTime versionTimestamp, XElement element, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, List<{5}> defaultValue)
        {{
            if (element == null)
                return defaultValue;
            List<{5}> list = new List<{5}>();
            int index = 0;
            foreach (XElement valueElement in element.Elements())
                list.Add(DeSerialize(versionTimestamp, valueElement, StreamSeeds, propertyPath + ""["" + (index++).ToString() + ""]"", null));
            return list;
        }}

        public static System.Collections.IDictionary DeSerializeDictionary(DateTime versionTimestamp, XElement element, GetDictionaryDelegate getDictionary, GetKeyDelegate getKey, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, System.Collections.IDictionary defaultValue)
        {{
            if (element == null)
                return defaultValue;
            System.Collections.IDictionary dictionary = getDictionary();
            foreach (XElement valueElement in element.Elements())
                dictionary.Add(getKey(valueElement.Attribute(""Key"").Value), DeSerialize(versionTimestamp, valueElement, StreamSeeds, propertyPath + ""["" + getKey(valueElement.Attribute(""Key"").Value) + ""]"", null));
            return dictionary;
        }}

        public static List<CIP4ReferenceType<{5}>> DeSerializeReferenceList(XElement element)
        {{
            if (element == null)
                return null;
            List<CIP4ReferenceType<{5}>> list = new List<CIP4ReferenceType<{5}>>();
            foreach (XElement valueElement in element.Elements())
            {{
                CIP4ReferenceType<{5}> refValue = new CIP4ReferenceType<{5}>();
                refValue.TargetExternalID = GetExternalIdFromReferenceElement(valueElement);
                list.Add(refValue);
            }}
            return list;
        }}

        public static CIP4ReferenceType<{5}> MakeCIP4Reference(XElement element, CIP4ReferenceType<{5}> defaultValue)
        {{
            string externalID = GetExternalIdFromReferenceElement(element);
            if (!string.IsNullOrEmpty(externalID))
                return new CIP4ReferenceType<{5}> {{ TargetExternalID = externalID }};
            else
                return defaultValue;
        }}

        public static string SerializeItem({5} value, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, ISerializerType serializerWrapper)
        {{
{7}
            StringWriter swrtr = new StringWriter();
            XmlTextWriter xwrtr = new XmlTextWriter(swrtr);
            // write header
            xwrtr.WriteStartElement(""Item"", Globals.ciiNS);
            xwrtr.WriteElementString(""Valid"", ""1"");
            xwrtr.WriteElementString(""ItemStatus"", ""100"");{3}
            xwrtr.WriteElementString(""ExternalID"", GetExternalIDForObject(value).ToString());
            xwrtr.WriteElementString(""ModifiedDate"", XmlConvert.ToString(DateTime.Now, Globals.DateTimeSerializationMode));
            Serialize(value, null, xwrtr, StreamSeeds, ""{5}"", serializerWrapper, true);
            xwrtr.WriteEndElement();
            return swrtr.ToString();
        }}

        internal static void Serialize({5} value, string keyValue, XmlTextWriter xwrtr, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, ISerializerType serializerWrapper, bool doVersioning)
        {{
{8}
            xwrtr.WriteStartElement(""{13}"", Globals.cioNS);
            if (!string.IsNullOrEmpty(keyValue))
                xwrtr.WriteAttributeString(""Key"", keyValue);
{2} 
            xwrtr.WriteEndElement();
        }}

        public static object SerializeToXElement(object value, ISerializerType serializerWrapper)
        {{
            StringWriter vswrtr = new StringWriter();
            XmlTextWriter vxwrtr = new XmlTextWriter(vswrtr);
            Serialize(({5})value, null, vxwrtr, null, null, serializerWrapper, false);
            XElement velement = XElement.Parse(vswrtr.ToString());
            return velement;
        }}

{18}
    }}
";

        private const string CSharpHeaderValueLiteral =
            // {0} - Type name
            // {1} - deserialize property assignment list
            // {2} - serialize property assignment list
            // {3} - Type full name (namespace.type)
            // {4} - Deserialize subtypes code (works for items and values)
            // {5} - Serialize subtypes for items
            // {6} - serialize subtypes for values
            // {7} - set private properties (horrible, uses reflection, makes a mockery of encapsulation etc.)
            // (8)-  stream handling for deserialize
            // {9} - newItem text - if the class is abstract, defines objectValue as "object objectValue = null;"
            // {10} - serializer name - either foo_Serializer or foo_Serializer<aType> (if it's generic)
            // {11} - type specific (as opposed to generic) name - the type name without the stuff in <> 
            // {12} - GetValue function - this is getting out of hand!
            // {13} - key list - this is really getting out of hand...
            // (14) - CheckVersion function
            // {15} - equalitycomparer
            // {16} - version handling
            // {17} - versionItem
            // {18} - GetItemVersionBody
            // {19} - first namespace name 
            // {20} - comment out itemElement if the item isn't versioned - yuk
@"
    public class {10} : {19}_SerializerBaseType
    {{
{14}
{13}
{12}
        public static {3} DeSerialize(DateTime versionTimestamp, XElement element, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, {3} defaultValue)
        {{
            if (element == null)
                return defaultValue;

            {20}XElement itemElement = null;

{4}
{9}
{8}     
{16}           
            return ({3})objectValue;
        }}

        public static {3} SetProperties(DateTime versionTimestamp, XElement itemElement, XElement element, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath)
        {{

{9}
            return ({3})objectValue;
        }}

        public static {3} GetItemVersion(long versionTimestamp, {3} previousVersion)
        {{
{18}
        }}

        public static {3} GetItemVersion(long versionTimestamp, {3}  previousVersion, Dictionary<string, object> Values)
        {{
{17}
            return ({3})objectValue;
        }}

        internal static List<{3}> DeSerializeList(DateTime versionTimestamp, XElement element, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, List<{3}> defaultValue)
        {{
            if (element == null)
                return defaultValue;
            List<{3}> list = new List<{3}>();
            foreach (XElement valueElement in element.Elements())
                list.Add(DeSerialize(versionTimestamp, valueElement, StreamSeeds, propertyPath, null));
            return list;
        }}

        public static System.Collections.IDictionary DeSerializeDictionary(DateTime versionTimestamp, XElement element, GetDictionaryDelegate getDictionary, GetKeyDelegate getKey, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, System.Collections.IDictionary defaultValue)
        {{
            if (element == null)
                return defaultValue;
            System.Collections.IDictionary dictionary = getDictionary();
            foreach (XElement valueElement in element.Elements())
                dictionary.Add(getKey(valueElement.Attribute(""Key"").Value), DeSerialize(versionTimestamp, valueElement, StreamSeeds, propertyPath, null));
            return dictionary;
        }}

        public static string SerializeItem({3} value, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, ISerializerType serializerWrapper)
        {{

            StringWriter swrtr = new StringWriter();
            XmlTextWriter xwrtr = new XmlTextWriter(swrtr);
            // write header
            Serialize(value, null, xwrtr, StreamSeeds, ""{3}"", serializerWrapper, true);
            return swrtr.ToString();
        }}

        internal static void Serialize({3} value, string keyValue, XmlTextWriter xwrtr, Dictionary<string, KeyValuePair<PropertyInfo, object>> StreamSeeds, string propertyPath, ISerializerType serializerWrapper, bool doVersioning)
        {{
{6}
            xwrtr.WriteStartElement(""{11}"", Globals.cioNS);
            if (!string.IsNullOrEmpty(keyValue))
                xwrtr.WriteAttributeString(""Key"", keyValue);
{2} 
            xwrtr.WriteEndElement();
        }}

        public static object SerializeToXElement(object value, ISerializerType serializerWrapper)
        {{
            StringWriter vswrtr = new StringWriter();
            XmlTextWriter vxwrtr = new XmlTextWriter(vswrtr);
            Serialize(({3})value, null, vxwrtr, null, null, serializerWrapper, false);
            XElement velement = XElement.Parse(vswrtr.ToString());
            return velement;
        }}

{15}
    }}
            ";

        const string CSharpExtensionMethodsLiteral =
@"    internal static class {0}ExtensionMethods
    {{
        public static {1}.{0} SetPrivateProperty(this {1}.{0} aThis, string propertyName, object aValue)
        {{
            Type aType = aThis.GetType();

            System.Reflection.PropertyInfo propInfo = aType.GetProperty(propertyName);

            if (propInfo == null)
                throw new ArgumentException(string.Format(""Property {{0}} is not available in type {{1}}"", propertyName, aType.Name));

            System.Reflection.MethodInfo setMethod = propInfo.GetSetMethod(true);

            if (setMethod == null)
                throw new ArgumentException(string.Format(""Property {{0}} in type {{1}} does not have a public, private, internal or protected set method"", propertyName, aType.Name));

            setMethod.Invoke(aThis, new object[] {{ aValue }});

            return aThis;
        }}
    }}
";

        private string SerializeHeader(Dictionary<string, TypeContextType> typesSeen)
        {
            if (string.IsNullOrEmpty(FirstNamespaceName))
                FirstNamespaceName = TheType.TheTypeNamespace.Replace(".", "_");

            SerializerBaseTypeBodyCase(TheType);

            string text;
            
            StringBuilder setPrivates = new StringBuilder();
            
            StringBuilder deSerializeSeedStream = new StringBuilder();

            StringBuilder setVersionPrivates = new StringBuilder();

            string deSerialzePropertiesText = DeSerializeProperties(setPrivates, deSerializeSeedStream);
            
            string serialzePropertiesText = Serializeproperties();

            string deSerialzeVersionPropertiesText = DeSerializeVersionProperties(setVersionPrivates);

            string newItem = TheType.TheType.IsAbstract ?
                "            object objectValue = null;":
                string.Format(CSharpNewItemLiteral,
                    TheType.TheTypeFullName,
                    deSerialzePropertiesText,
                    setPrivates);
            
            string versionItem = TheType.TheType.IsAbstract ?
                "            object objectValue = null;" :
                string.Format(CSharpNewItemLiteral,
                    TheType.TheTypeFullName,
                    deSerialzeVersionPropertiesText,
                    setVersionPrivates);
            // ToDo: should inspect these very carefully at some point - some f the parameters are not being used
// ReSharper disable FormatStringProblem
            if (GetExternalID() == string.Empty)
                text = string.Format(CSharpHeaderValueLiteral, 
                    TheType.TheTypeName,
                    deSerialzePropertiesText,
                    serialzePropertiesText,
                    TheType.TheTypeFullName,
                    DeSerializeSubtypes(),
                    SerializeSubtypes(),
                    SerializeSubtypes2(),
                    setPrivates,
                    deSerializeSeedStream,
                    newItem,
                    TheType.TypeSerializerName,
                    TheType.TheTypeSpecificName,
                    MakeGetValue(), 
                    MakeKeys(),
                    MakeCheckVersion(),
                    GetEqualityComparer(),
                    DeSerializeVersionHandling(),
                    versionItem,
                    GetItemVersionBody(),
                    FirstNamespaceName,
                    TheType.VersionProperty != null? string.Empty : "// ");
            else
                text = string.Format(CSharpHeaderItemLiteral,
                    TheType.TheTypeName,
                    deSerialzePropertiesText,
                    serialzePropertiesText, 
                    string.Empty, 
                    GetExternalID(),
                    TheType.TheTypeFullName,
                    DeSerializeSubtypes(),
                    SerializeSubtypes(),
                    SerializeSubtypes2(),
                    setPrivates,
                    deSerializeSeedStream,
                    newItem,
                    TheType.TypeSerializerName,
                    TheType.TheTypeSpecificName,
                    MakeExternalIDGets(),
                    MakeGetValue(),
                    MakeKeys(),
                    MakeCheckVersion(),
                    GetEqualityComparer(),
                    DeSerializeVersionHandling(),
                    versionItem,
                    GetItemVersionBody(),
                    FirstNamespaceName);
// ReSharper restore FormatStringProblem
            if (setPrivates.Length > 0)
                text += string.Format(CSharpExtensionMethodsLiteral,
                    TheType.TheTypeName,
                    TheType.TheTypeNamespace);
            if (!typesSeen.ContainsKey(TheType.TheTypeFullName))
                typesSeen.Add(TheType.TheTypeFullName, TheType);
            foreach (TypeContextType usedType in UsedTypes.Values)
            {
                if (typesSeen.ContainsKey(usedType.TheTypeFullName))
                    continue;
                text += new XmlSerializerBuilderType(usedType).SerializeHeader(typesSeen);
            }
            if (TheType.TheTypeSpecificName == "CIP4ScalablePropertyType")
            {
                // What we want is a ChunkType with its generic parameter type being the same as the
                // generic parameter type used for the CIP4ScalablePropertyTYpe. We generate a serializer 
                // for the parameterized ChunkType - the code generated above by the XmlSerializerBuilderType
                // is hopefully smart enough to call it where appropriate.
                Type anOriginal = typeof(ChunkType<>).MakeGenericType(TheType.TheOtherType);
                TypeContextType usedType = new TypeContextType { OriginalType = anOriginal };
                if (!typesSeen.ContainsKey(usedType.TheTypeFullName))
                    text += new XmlSerializerBuilderType(new TypeContextType { OriginalType = anOriginal }).SerializeHeader(typesSeen);
            }
            return text;
        }
        
        #region externalid handling

        private const string CSharpGetExternalIDLiteral =
            // {0} - list of xml property name assignments, 
            // {1} - list of object property name assignements
            // {2} - fullname of the type
@"        public static ExternalIDType GetExternalID(XElement itemElement)
        {{
            XElement element = null;
            if (itemElement.Name == XName.Get(""Item"", Globals.ciiNS))
            {{
                element = itemElement.LastElement();
            }}
            else
                element = itemElement;
            List<object> parts = new List<object>();
{0}

            ExternalIDType result = ExternalIDType.ExternalIDFromParts(parts);

            if (result == null)
                return ExternalIDType.ExternalIDFromEscapedString(StringValue(itemElement.Element(XName.Get(""ExternalID"", Globals.ciiNS)), string.Empty));

            return result;
        }}
        public static ExternalIDType GetExternalIDForObject({2} value)
        {{
            List<object> parts = new List<object>();
{1}
            return ExternalIDType.ExternalIDFromParts(parts);
        }}
";

        private string MakeExternalIDGets()
        {
            StringBuilder dataGets = new StringBuilder();

            StringBuilder objectGets = new StringBuilder();

            foreach (PropertyInfo prop in GetExternalIDProperties(TheType))
            {
                BuildExternalIdPaths(prop, string.Empty, string.Empty, dataGets, objectGets);
            }

            string result = string.Format(CSharpGetExternalIDLiteral,
                dataGets,
                objectGets,
                TheType.TheTypeFullName);

            return result;

        }

        private const string CSharpGetExternalIDFromXmlLiteral =  // {0} is the property name
            @"            parts.Add(StringValue(element.{0}, string.Empty));
";

        private const string CSharpGetExternalIDFromObjectLiteral =  // {0} is the property name
            @"            parts.Add(value.{0});
";

        // these get concatenated together to build up a path to the property
        private const string CSharpGetExternalIDFromXmlPathSegementLiteral =  // {0} is the property name
            @"Element(XName.Get(""{0}"", Globals.cioNS))";

        private void BuildExternalIdPaths(PropertyInfo prop, string qualifier, string path, StringBuilder dataGets, StringBuilder objectGets)
        {
            TypeContextType propType = new TypeContextType { OriginalType = prop.PropertyType };

            if (ValueType(propType.TheType))
            {
                dataGets.Append(string.Format(CSharpGetExternalIDFromXmlLiteral,
                    path + string.Format(CSharpGetExternalIDFromXmlPathSegementLiteral, propType.TheTypeName)));
                objectGets.Append(string.Format(CSharpGetExternalIDFromObjectLiteral, qualifier + prop.Name));
            }
            else
            {
                foreach (PropertyInfo property in GetExternalIDProperties(propType))
                {
                    if (!IncludeThisProperty(property))
                        continue;
                    BuildExternalIdPaths(
                        property, 
                        qualifier + prop.Name + ".", 
                        path + string.Format(CSharpGetExternalIDFromXmlPathSegementLiteral, prop.Name) + "." +
                        path + string.Format(CSharpGetExternalIDFromXmlPathSegementLiteral, propType.TheTypeName) + ".", 
                        dataGets, 
                        objectGets);
                }
            }
        }

        #endregion


        #region Deserialize

        private const string CSharpEqualityComparerWithExternalIDLiteral =
@"
        public class EqualityComparer : IEqualityComparer<{0}>
        {{
            public bool Equals({0} x, {0} y)
            {{
                return GetExternalIDForObject(x).ToString() == GetExternalIDForObject(y).ToString();
            }}

            public int GetHashCode({0} obj)
            {{
                return GetExternalIDForObject(obj).ToString().GetHashCode();
            }}
        }}
";

        private const string CSharpEqualityComparerWithOutExternalIDLiteral =
@"
        public class EqualityComparer : IEqualityComparer<{0}>
        {{
            public bool Equals({0} x, {0} y)
            {{
                return SerializeToXElement(x, null).ToString() == SerializeToXElement(y, null).ToString();
            }}

            public int GetHashCode({0} obj)
            {{
                return SerializeToXElement(obj, null).ToString().GetHashCode();
            }}
        }}
";

        protected string GetEqualityComparer()
        {
            if (TheType.HasExternalID)
                return string.Format(CSharpEqualityComparerWithExternalIDLiteral, TheType.TheTypeFullName);

            return string.Format(CSharpEqualityComparerWithOutExternalIDLiteral, TheType.TheTypeFullName);
        }

        // {0} - type name
        // {1} - property name
        private const string CSharpVersionHandlingLiteral =
            @"
            CIP4ItemVersionsType versions = new CIP4ItemVersionsType(versionTimestamp, SetProperties(versionTimestamp, itemElement, element, null, propertyPath));

            XElement versionsElement = element.Element(XName.Get(""{1}"", Globals.cioNS));
            if (versionsElement != null)
            {{
                foreach (XElement velement in versionsElement.Elements())
                {{
                    Dictionary<string, object> values = new Dictionary<string, object>();
                    XAttribute versionAttribute = velement.Attribute(""_version"");
                    if (versionAttribute == null)
                        throw new ArgumentException(string.Format(""Version information was incorrectly serialized, version _version attribute is missing - see {0}"", velement));
                    string version = versionAttribute.Value;
                    foreach (XElement vvelement in velement.Elements())
                    {{
                        values.Add(vvelement.Name.LocalName, ValueFromXElement(vvelement.Name.LocalName, vvelement, DeSerializeFromXElement));
                    }}
                    versions.AddVersion(version, values);
                }}
            }}

            objectValue.{1}= versions;

";

        private string DeSerializeVersionHandling()
        {
            PropertyInfo versionsProp = VersionProperty;
            if (versionsProp != null)
            {
                Type versionsPropType = TheType.TheType;
                if (versionsProp != null && !(versionsPropType == TheType.TheType || TheType.TheType.IsSubclassOf(versionsPropType)))
                    throw new ArgumentException(string.Format("The version property {0} for {1} must have a must have {1} as its generic parameter type ",
                        versionsProp.Name, TheType));
                return string.Format(CSharpVersionHandlingLiteral,
                    versionsPropType.FullName,
                    versionsProp.Name);
            }
            return "            // no version handling for this type";
        }

        
        
        
        private const string CSharpDeSubtypesLiteral =
@"            if (element.Name.LocalName == ""{0}"")
                return {1}.DeSerialize(versionTimestamp, element, StreamSeeds, propertyPath, null);
";

        private const string CSharpCheckElementLiteral =
@"            if (element.Name.LocalName != ""{0}"")
                return null;
";

        private string DeSerializeSubtypes()
        {
            string text = string.Empty;
            foreach (Type t in TheType.TheType.Assembly.GetTypes())
            {
                if (!TheType.TheType.IsAssignableFrom(t) || t == TheType.TheType)
                    continue;
                TypeContextType tc = new TypeContextType { OriginalType = t };
                text += string.Format(CSharpDeSubtypesLiteral,
                    tc.TheTypeName,
                    tc.TypeSerializerName);
                if (!UsedTypes.ContainsKey(tc.TheTypeFullName))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
            }

            if (!TheType.TheTypeName.Contains("<"))  // this is a hack to cater for the way that scalable properties deal with type - basically scalable properties use the wrong type name at times
                text += string.Format(CSharpCheckElementLiteral,
                    TheType.TheTypeName);
            else
                Console.WriteLine("funny name");
            return text;
        }

        private string DeSerializeProperties(StringBuilder setPrivates, StringBuilder deSerializeSeedStream)
        {
            string text = string.Empty;
            foreach (PropertyInfo property in TheType.TheType.GetProperties())
            {
                CIP4ExternalIDAttribute extIDAtt = CIP4ExternalIDAttribute.GetAttribute(property);
                if (!IncludeThisProperty(property) && extIDAtt == null)
                    continue;
                if (property.GetSetMethod(true) == null)
                    continue;
                if (property.PropertyType.GetInterface("IList`1") != null || property.PropertyType.Name == "HashSet`1")
                    text += DeserializeMV(property);
                else if (property.PropertyType.GetInterface("IDictionary") != null)
                    text += DeserializeMVDictionary(property);
                else
                    text += DeserializeSV(property, setPrivates, deSerializeSeedStream);
            }
            return text;
        }

        private string DeSerializeVersionProperties(StringBuilder setPrivates)
        {
            string text = string.Empty;
            foreach (PropertyInfo property in TheType.TheType.GetProperties())
            {
                CIP4ExternalIDAttribute extIDAtt = CIP4ExternalIDAttribute.GetAttribute(property);
                if (!IncludeThisProperty(property) && extIDAtt == null)
                    continue;
                if (property.GetSetMethod(true) == null)
                    continue;
                if (!VersionThisType(property.PropertyType))
                    continue;
                if (property.PropertyType.GetInterface("IList`1") != null || property.PropertyType.Name == "HashSet`1")
                    text += DeserializeMVVersion(property);
                else if (property.PropertyType.GetInterface("IDictionary") != null)
                    text += DeserializeDictionaryVersion(property);
                else
                    text += DeserializeSVVersion(property, setPrivates);
            }
            return text;
        }

        private const string CSharpSvVersionLiteral =
            @"
            if (Values.ContainsKey(""{0}""))
                objectValue.{0} = {1}Value(Values[""{0}""].ToString(), objectValue.{0});
            else
                objectValue.{0} = previousVersion.{0};";

        private const string CSharpDeEnumVersionLiteral =
            @"
            if (Values.ContainsKey(""{0}""))
                objectValue.{0} = ({1})GetEnum(Values[""{0}""].ToString(), typeof({1}), objectValue.{0});
            else
                objectValue.{0} = previousVersion.{0};";

        private const string CSharpCompoundSvVersionLiteral =
            @"
            if (Values.ContainsKey(""{0}""))
                objectValue.{0} = {1}.DeSerialize(new DateTime(versionTimestamp), (XElement)Values[""{0}""], null, null, null);
            else
                objectValue.{0} = {1}.GetItemVersion(versionTimestamp, previousVersion.{0});
";

        private const string CSharpPrivatesVersionLiteral =
            @"
            if (Values.ContainsKey(""{0}""))
                objectValue.SetPrivateProperty(""{0}"", {1}Value(Values[""{0}""].ToString(), default({1})));
            else
                objectValue.SetPrivateProperty(""{0}"", previousVersion.{0});

";


        private string DeserializeSVVersion(PropertyInfo property, StringBuilder setPrivates)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };
            if (actualType.TheType == typeof(Stream))
            {
                return string.Empty;
            }
            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Empty;
            }
            if (actualType.HostTypeName == "CIP4ScalablePropertyType")
            {
                return string.Empty;
            }
            if (actualType.TheType.IsEnum)
            {
                return string.Format(CSharpDeEnumVersionLiteral,
                    property.Name,
                    actualType.TheTypeFullName);
            }
            if (ValueType(actualType.TheType))
            {
                if (property.GetSetMethod() == null)
                {
                    setPrivates.Append(string.Format(CSharpPrivatesVersionLiteral,
                        property.Name,
                        actualType.TheTypeName));
                    return string.Empty;
                }
                return string.Format(CSharpSvVersionLiteral,
                    property.Name,
                    actualType.TheTypeName);
            }
            if (IncludeThisType(actualType.TheType))
            {
                return string.Format(CSharpCompoundSvVersionLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            return string.Empty;
        }

        private const string CSharpMvSimpleVersionLiteral =
            @"
            objectValue.{0} = MvVersion <{1}{2}>(""{0}"", Values, previousVersion.{0});
";

        private const string CSharpMvSimpleArrayVersionLiteral =
            @"
            objectValue.{0} = MvVersion <{1}{2}>(""{0}"", Values, previousVersion.{0}).ToArray();
";

        private const string CSharpMvSimpleHashSetVersionLiteral =
            @"
            objectValue.{0} = new HashSet<{1}>(MvVersion <{1}>(""{0}"", Values, previousVersion.{0}.ToList()));
";

        private const string CSharpMvCompoundVersionLiteral =
            @"
            objectValue.{0} = MvVersion<{1}>(""{0}"", Values, previousVersion.{0});
";

        private string DeserializeMVVersion(PropertyInfo property)
        {
            if (property.PropertyType.GetInterface("IList`1") == null &&
                !property.PropertyType.IsArray &&
                property.PropertyType.Name != "HashSet`1")
                return string.Empty;
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };

            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Empty;
            }
            if (ValueType(property.PropertyType))
            {
                if (property.PropertyType.IsArray)
                    return string.Format(CSharpMvSimpleArrayVersionLiteral,
                        property.Name,
                        actualType.TheTypeFullName,
                        actualType.IsNullable ? "?" : string.Empty);
                if (property.PropertyType.Name == "HashSet`1")
                    return string.Format(CSharpMvSimpleHashSetVersionLiteral,
                        property.Name,
                        actualType.TheTypeSpecificName);
                return string.Format(CSharpMvSimpleVersionLiteral,
                    property.Name,
                    actualType.TheTypeFullName,
                    actualType.IsNullable ? "?" : string.Empty);
            }
            if (IncludeThisType(actualType.TheType))
            {
                return string.Format(CSharpMvCompoundVersionLiteral,
                    property.Name,
                    actualType.TheTypeFullName);
            }
            return string.Empty;
        }

        private const string CSharpDictionarySimpleVersionLiteral =
            @"
            objectValue.{0} = MvVersion <{1},{2}{3}>(""{0}"", Values, previousVersion.{0});
";

        private const string CSharpDictionaryCompoundVersionLiteral =
            @"
            objectValue.{0} = MvVersion<{1},{2}>(""{0}"", Values, previousVersion.{0});
";

        private string DeserializeDictionaryVersion(PropertyInfo property)
        {
            if (!property.PropertyType.IsGenericType)
                return string.Empty;

            TypeContextType valueType = new TypeContextType { OriginalType = property.PropertyType };
            TypeContextType keyType = new TypeContextType { OriginalType = property.PropertyType.GetGenericArguments()[0] };

            if (valueType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Empty;
            }
            if (ValueType(valueType.TheType))
            {
                return string.Format(CSharpDictionarySimpleVersionLiteral,
                    property.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeFullName,
                    valueType.IsNullable ? "?" : string.Empty);
            }
            if (IncludeThisType(valueType.TheType))
            {
                return string.Format(CSharpDictionaryCompoundVersionLiteral,
                    property.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeFullName);
            }
            return string.Empty;
        }


        private const string CSharpDeSimplePrivateLiteral = // {0} property name  - {1} property type
@"
            objectValue.SetPrivateProperty(""{0}"", {1}Value(element.Element(XName.Get(""{0}"", Globals.cioNS)), default({1})))";

        private const string CSharpDeSimpleLiteral =
@"                objectValue.{0} = {1}Value(element.Element(XName.Get(""{0}"", Globals.cioNS)), objectValue.{0});
";

        private const string CSharpDeExternalIDLiteral =
@"                objectValue.{0} = {1}Value(GetExternalIDFromElements(""{0}"", element, itemElement, GetExternalID), objectValue.{0});
";

        private const string CSharpDeEnumLiteral =
@"                objectValue.{0} = ({1})GetEnum(element.Element(XName.Get(""{0}"", Globals.cioNS)), typeof({1}), objectValue.{0});
";

        private const string CSharpDeCompoundLiteral =
@"                objectValue.{0} = CIP4.Serializer.{1}.DeSerialize(versionTimestamp, element.Element(XName.Get(""{0}"", Globals.cioNS)).FirstElement(), StreamSeeds, propertyPath + "".{0}"", objectValue.{0});
";

        private const string CSharpDeReferenceLiteral =
@"                objectValue.{0} = {1}.MakeCIP4Reference(element.Element(XName.Get(""{0}"", Globals.cioNS)).FirstElement(), objectValue.{0});

";

        private const string CSharpDeStreamLiteral =
@"
            if (StreamSeeds != null && element.Element(XName.Get(""{0}"", Globals.cioNS)) != null)
                StreamSeeds.Add(propertyPath + "".{0}"", new KeyValuePair<PropertyInfo, object> (typeof({1}).GetProperty(""{0}""), objectValue));

";
        private string DeserializeSV(PropertyInfo property, StringBuilder setPrivates, StringBuilder deSerializeSeedStream)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };
            if (actualType.TheType == typeof(Stream))
            {
                deSerializeSeedStream.Append(string.Format(CSharpDeStreamLiteral,
                    property.Name,
                    GetActualType(property.DeclaringType).FullName));
            }
            else if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Format(CSharpDeReferenceLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            else if (actualType.HostTypeName == "CIP4ScalablePropertyType")
            {
                return string.Format(CSharpDeCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            else if (actualType.TheType.IsEnum)
            {
                return string.Format(CSharpDeEnumLiteral,
                    property.Name,
                    actualType.TheTypeFullName);
            }
            else if (ValueType(actualType.TheType))
            {
                if (CIP4ExternalIDAttribute.GetAttribute(property) != null && GetExternalIDProperties(TheType).Count == 1)
                {
                    return string.Format(CSharpDeExternalIDLiteral,
                        property.Name,
                        actualType.TheTypeName);
                }
                if (property.GetSetMethod() == null)
                {
                    setPrivates.Append(string.Format(CSharpDeSimplePrivateLiteral,
                        property.Name,
                        actualType.TheTypeName));
                    return string.Empty;
                }
                return string.Format(CSharpDeSimpleLiteral,
                    property.Name,
                    actualType.TheTypeName);
            }
            else if (IncludeThisType(actualType.TheType))
            {
                return string.Format(CSharpDeCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            return string.Empty;
        }

        private const string CSharpDeMvCompoundLiteral =
@"                objectValue.{0} = CIP4.Serializer.{1}.DeSerializeList(versionTimestamp, element.Element(XName.Get(""{0}"", Globals.cioNS)), StreamSeeds, propertyPath + "".{0}"", objectValue.{0});
";

        private const string CSharpDeMvArrayCompoundLiteral =
@"                XElement {0}Value = element.Element(XName.Get(""{0}"", Globals.cioNS));
                if ({0}Value != null)
                    objectValue.{0} = CIP4.Serializer.{1}.DeSerializeList(versionTimestamp, {0}Value, StreamSeeds, propertyPath + "".{0}"", null).ToArray();
";

        private const string CSharpDeMvSimpleLiteral =
@"                objectValue.{0} = ListValues<{1}>(element.Element(XName.Get(""{0}"", Globals.cioNS)));
";

        private const string CSharpDeMvSimpleNullableLiteral =
@"                objectValue.{0} = ListValues<{1}?>(element.Element(XName.Get(""{0}"", Globals.cioNS)));
";

        private const string CSharpDeMvSimpleHashSetLiteral =
@"                objectValue.{0} = HashSetValues<{1}>(element.Element(XName.Get(""{0}"", Globals.cioNS)));
";

        private const string CSharpDeMvArraySimpleLiteral =
@"                objectValue.{0} = ArrayValues<{1}>(element.Element(XName.Get(""{0}"", Globals.cioNS)));
";

        private const string CSharpDeMvReferenceLiteral =
@"                objectValue.{0} = CIP4.Serializer.{1}.DeSerializeReferenceList(element.Element(XName.Get(""{0}"", Globals.cioNS)));
";

        private string DeserializeMV(PropertyInfo property)
        {
            if (property.PropertyType.GetInterface("IList`1") == null &&
                !property.PropertyType.IsArray && 
                property.PropertyType.Name != "HashSet`1")
                return string.Empty;
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };

            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Format(CSharpDeMvReferenceLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            if (actualType.HostTypeName == "CIP4ScalablePropertyType")
            {
                if (!UsedTypes.ContainsKey(actualType.TheTypeFullName))
                    UsedTypes.Add(actualType.TheTypeFullName, actualType);
                if (property.PropertyType.IsArray)
                    return string.Format(CSharpDeMvArrayCompoundLiteral,
                        property.Name,
                        actualType.TypeSerializerName);
                return string.Format(CSharpDeMvCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            if (ValueType(actualType.TheType))
            {
                if (property.PropertyType.IsArray)
                    return string.Format(CSharpDeMvArraySimpleLiteral,
                        property.Name,
                        actualType.TheTypeFullName);
                if (property.PropertyType.Name == "HashSet`1")
                    return string.Format(CSharpDeMvSimpleHashSetLiteral,
                        property.Name,
                        actualType.TheTypeSpecificName);
                if (actualType.IsNullable)
                    return string.Format(CSharpDeMvSimpleNullableLiteral,
                        property.Name,
                        actualType.TheTypeFullName);
                return string.Format(CSharpDeMvSimpleLiteral,
                    property.Name,
                    actualType.TheTypeFullName);
            }
            if (IncludeThisType(actualType.TheType))
            {
                if (!UsedTypes.ContainsKey(actualType.TheTypeFullName))
                    UsedTypes.Add(actualType.TheTypeFullName, actualType);
                if (property.PropertyType.IsArray)
                    return string.Format(CSharpDeMvArrayCompoundLiteral,
                        property.Name,
                        actualType.TypeSerializerName);
                return string.Format(CSharpDeMvCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            return string.Empty;
        }


        private const string CSharpDeMvDictCompoundLiteral =  // {0} - property name {1} qualified property type name {2} unqualified property type name {3} keytype
@"                objectValue.{0} = (Dictionary<{3}, {1}>)
                    CIP4.Serializer.{2}.DeSerializeDictionary(
                        versionTimestamp, 
                        element.Element(XName.Get(""{0}"", Globals.cioNS)),
                        delegate() {{ return new Dictionary<{3}, {1}>(); }},
                        delegate(string keyValue) {{ return {4}Value(keyValue, default({3}));}}
                        , StreamSeeds, propertyPath + "".{0}"", objectValue.{0});
";

        private const string CSharpDeMvDictSimpleLiteral = // {0} property name {1} property value type {2} key type
@"                objectValue.{0} = (Dictionary<{2}, {1}{4}>)
                    GetObjectDictionary(
                        element.Element(XName.Get(""{0}"", Globals.cioNS)),
                        typeof({1}{4}),
                        delegate() {{ return new Dictionary<{2}, {1}{4}>(); }},
                        delegate(string keyValue) {{ return {3}Value(keyValue, default({2})); }},
                        objectValue.{0});
";

        private string DeserializeMVDictionary(PropertyInfo property)
        {
            if (!property.PropertyType.IsGenericType)
                return string.Empty;
            TypeContextType valueType = new TypeContextType { OriginalType = property.PropertyType };
            TypeContextType keyType = new TypeContextType { OriginalType = property.PropertyType.GetGenericArguments()[0] };
            if (valueType.HostTypeName == "CIP4ReferenceType")
                throw new NotImplementedException("Generated serializer doesn't support dictionaries of references at this time");
            if (ValueType(valueType.TheType))
                return string.Format(CSharpDeMvDictSimpleLiteral,
                    property.Name,
                    valueType.TheTypeFullName,
                    keyType.TheTypeFullName,
                    keyType.TheTypeName,
                    valueType.IsNullable? "?" : string.Empty);
            if (IncludeThisType(valueType.TheType))
            {
                if (!UsedTypes.ContainsKey(valueType.TheTypeFullName))
                    UsedTypes.Add(valueType.TheTypeFullName, valueType);
                // {0} - property name {1} qualified property type name {2} unqualified property type name {3} keytype
                return string.Format(CSharpDeMvDictCompoundLiteral,
                    property.Name,
                    valueType.TheTypeFullName,
                    valueType.TypeSerializerName,
                    keyType.TheTypeFullName,
                    keyType.TheTypeName);
            }
            return string.Empty;
        }

#endregion

#region Serialize

        private const string CSharpSubtypesLiteral =
@"            if (value is {0})
                return {1}.SerializeItem(({0})value, StreamSeeds, serializerWrapper);
";

        private string SerializeSubtypes()
        {
            string text = string.Empty;
            foreach (Type t in TheType.TheType.Assembly.GetTypes())
            {
                if (!TheType.TheType.IsAssignableFrom(t) || t == TheType.TheType)
                    continue;
                TypeContextType tc = new TypeContextType { OriginalType = t };
                text += string.Format(CSharpSubtypesLiteral,
                    tc.TheTypeFullName,
                    tc.TypeSerializerName);
            }
            return text;
        }

        private const string CSharpSubtypes2Literal =
@"            if (value is {0})
            {{
                {1}.Serialize(({0})value, keyValue, xwrtr, StreamSeeds, propertyPath, serializerWrapper, doVersioning);
                return;
            }}
";

        private string SerializeSubtypes2()
        {
            string text = string.Empty;
            foreach (Type t in TheType.TheType.Assembly.GetTypes())
            {
                if (!TheType.TheType.IsAssignableFrom(t) || t == TheType.TheType)
                    continue;
                TypeContextType tc = new TypeContextType { OriginalType = t };
                text += string.Format(CSharpSubtypes2Literal,
                    tc.TheTypeFullName,
                    tc.TypeSerializerName);
            }
            return text;
        }

        private string GetExternalID()
        {
            foreach (PropertyInfo property in TheType.TheType.GetProperties())
                if (CIP4ExternalIDAttribute.GetAttribute(property) != null)
                {
                    if (property.PropertyType == typeof(string))
                        return "value." + property.Name;

                    return "XmlConvert.ToString(value." + property.Name + ")";
                }
            return string.Empty;
        }

        private const string CSharpVersionsPropertySerializeLiteral =
@"            if (doVersioning && value.{0} != null && value.{0}.Versions.Count > 0)
                SerializeVersions<{1}>(""{0}"", value.{0}, xwrtr, SerializeToXml);

";

        private string Serializeproperties()
        {
            string text = string.Empty;
            foreach (PropertyInfo property in WrapperSerializerType.SortedProperties(TheType.TheType.GetProperties()).Values)
            {
                CIP4ExternalIDAttribute extIDAtt = CIP4ExternalIDAttribute.GetAttribute(property);
                if (!IncludeThisProperty(property) && extIDAtt == null)
                    continue;
                if (property.PropertyType.GetInterface("IList`1") != null || property.PropertyType.Name == "HashSet`1")
                    text += SerializeMV(property);
                else if (property.PropertyType.GetInterface("IDictionary") != null)
                    text += SerializeMVDictionary(property);
                else
                    text += SerializeSV(property);
            }
            if (VersionProperty != null)
                text += string.Format(CSharpVersionsPropertySerializeLiteral,
                    VersionProperty.Name, 
                    TheType.TheTypeFullName);
            return text;
        }

        private const string CSharpDictionaryVersionsLiteral =
@"
                if (value.Versions != null)
                    value.Versions.MakeDictionaryDelta(""{0}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, null);";

        protected string SerializeDictionaryVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            return string.Format(CSharpDictionaryVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType));
        }

        private const string CSharpDictionaryNowNullVersionsLiteral =
@"
                else if (value.Versions != null)
                    value.Versions.MakeDictionaryDelta(""{0}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, null);";

        protected string SerializeDictionaryNowNullVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            return string.Format(CSharpDictionaryNowNullVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType));
        }

        private const string CSharpMvVersionsLiteral =
@"
                if (value.Versions != null)
                    value.Versions.MakeMvDelta(""{0}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, {3});";

        protected string SerializeMVVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            string serializerBuilder = 
                ValueType(actualType.TheType)
                   ? "null"
                   : string.Format("{0}.SerializeToXElement", actualType.TypeSerializerName);
            return string.Format(CSharpMvVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType),
                serializerBuilder);
        }

        private const string CSharpMvNowNullVersionsLiteral =
@"
                if (value.Versions != null)
                    value.Versions.MakeMvDelta(""{0}"", value.{0}, value.Versions.GetCurrent<{1}>().{0}, {2}, {3});";

        protected string SerializeMVNowNullVersions(PropertyInfo property, TypeContextType actualType)
        {
            if (VersionProperty == null)
                return string.Empty;
            string serializerBuilder =
                ValueType(actualType.TheType)
                   ? "null"
                   : string.Format("{0}.SerializeToXElement", actualType.TypeSerializerName);
            return string.Format(CSharpMvNowNullVersionsLiteral,
                property.Name,
                TheType.TheTypeFullName,
                GetIEqualityComparer(actualType),
                serializerBuilder);
        }



        private const string CSharpMvCompoundSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count() > 0)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteAttributeString(""Count"", value.{0}.Count().ToString());
                int index = 0;
                foreach ({2} propertyValue in value.{0})
                    {1}.Serialize(propertyValue, null, xwrtr, StreamSeeds, propertyPath + "".{0}["" + (++index).ToString() + ""]"", serializerWrapper, doVersioning);
                xwrtr.WriteEndElement();{3}
            }}{4}
";

        private const string CSharpMvSimpleSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count() > 0)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteAttributeString(""Count"", value.{0}.Count().ToString());
                foreach ({3} propertyValue in value.{0})
                    xwrtr.WriteElementString(""{1}"", {2});
                xwrtr.WriteEndElement();{4}
            }}{5}
";

        private const string CSharpMvReferenceSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count() > 0)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteAttributeString(""Count"", value.{0}.Count().ToString());
                foreach (CIP4ReferenceType<{1}> propertyValue in value.{0})
                {{
                    xwrtr.WriteStartElement(""CIP4ReferenceType"");
                    xwrtr.WriteStartElement(""TargetExternalID"");
                    xwrtr.WriteElementString(""String"", ((CIP4ReferenceBaseType)propertyValue).TargetExternalID);
                    xwrtr.WriteEndElement();  // TargetExternalID
                    xwrtr.WriteEndElement();  // CIP4ReferenceType
                }}
                xwrtr.WriteEndElement();
            }}
";

        private string SerializeMV(PropertyInfo prop)
        {
            if (prop.PropertyType.GetInterface("IList`1") == null && 
                !prop.PropertyType.IsArray &&
                prop.PropertyType.Name != "HashSet`1")
                return string.Empty;
            TypeContextType actualType = new TypeContextType { OriginalType = prop.PropertyType };
            if (actualType.HostTypeName == "CIP4ReferenceType")
                return string.Format(CSharpMvReferenceSerializeLiteral,
                    prop.Name,
                    actualType.TheType.FullName);
            if (ValueType(actualType.TheType))
            {
                if (prop.PropertyType.Name == "HashSet`1")
                    return string.Format(CSharpMvSimpleSerializeLiteral,
                        prop.Name,
                        actualType.TheTypeSpecificName,
                        SerializeSimpleMVValue(prop, "propertyValue"),
                        actualType.TheTypeSpecificName,
                        SerializeMVVersions(prop, actualType),
                        SerializeMVNowNullVersions(prop, actualType));
                return string.Format(CSharpMvSimpleSerializeLiteral,
                    prop.Name,
                    actualType.TheTypeName,
                    SerializeSimpleMVValue(prop, "propertyValue"),
                    actualType.TheTypeFullName,
                    SerializeMVVersions(prop, actualType),
                    SerializeMVNowNullVersions(prop, actualType));
            }
            if (IncludeThisType(actualType.TheType))
            {
                foreach (TypeContextType tc in (TargetTypes(actualType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpMvCompoundSerializeLiteral,
                    prop.Name,
                    actualType.TypeSerializerName,
                    actualType.TheTypeFullName,
                    SerializeMVVersions(prop, actualType),
                    SerializeMVNowNullVersions(prop, actualType));
            }
            return string.Empty;
        }

        const string CSharpMvDictCompoundSerializeLiteral =  // {0} property name  // {1} key type {2} qualified valuetype name {3} valuetype name
@"            if (value.{0} != null && value.{0}.Count > 0)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteAttributeString(""Count"", value.{0}.Count.ToString());
                foreach (KeyValuePair<{1}, {2}> propertyValue in value.{0})
                    {3}.Serialize(propertyValue.Value, propertyValue.Key.ToString(), xwrtr, StreamSeeds, propertyPath + "".{0}["" + propertyValue.Key.ToString() + ""]"", serializerWrapper, doVersioning);
                xwrtr.WriteEndElement();{4}
            }}{5}
";

        private const string CSharpMvDictSimpleSerializeLiteral =
@"            if (value.{0} != null && value.{0}.Count > 0)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteAttributeString(""Count"", value.{0}.Count.ToString());
                foreach (KeyValuePair<{1}, {4}> propertyValue in value.{0})
                {{
                    xwrtr.WriteStartElement(""{2}"");
                    xwrtr.WriteAttributeString(""Key"", {5});
                    xwrtr.WriteValue({3});
                    xwrtr.WriteEndElement();
                }}
                xwrtr.WriteEndElement();{6}
            }}{7}
";


        private string SerializeMVDictionary(PropertyInfo prop)
        {
            if (!prop.PropertyType.IsGenericType)
                return string.Empty;
            TypeContextType valueType = new TypeContextType { OriginalType = prop.PropertyType };
            TypeContextType keyType = new TypeContextType { OriginalType = prop.PropertyType.GetGenericArguments()[0] };

            if (valueType.HostTypeName == "CIP4ReferenceType")
                throw new NotImplementedException("Dictionaries of references are not implemented yet");
            if (ValueType(valueType.TheType))
                return string.Format(CSharpMvDictSimpleSerializeLiteral,
                    prop.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeName,
                    SerializeSimpleMVValue(prop, "propertyValue.Value"),
                    valueType.TheTypeFullName + (valueType.IsNullable ? "?" : string.Empty),
                    SerializeSimpleMVValue(prop, "propertyValue.Key"),
                    SerializeDictionaryVersions(prop, valueType),
                    SerializeDictionaryNowNullVersions(prop, valueType));
            if (IncludeThisType(valueType.TheType))
            {
                foreach (TypeContextType tc in (TargetTypes(valueType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpMvDictCompoundSerializeLiteral,
                    prop.Name,
                    keyType.TheTypeFullName,
                    valueType.TheTypeFullName,
                    valueType.TypeSerializerName,
                    SerializeDictionaryVersions(prop, valueType),
                    SerializeDictionaryNowNullVersions(prop, valueType));
            }
            return string.Empty;
        }

        private const string CSharpStreamTypeSerializeLiteral =
@"            if (value.{0} != null)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteElementString(""String"", propertyPath + "".{0}"");
                xwrtr.WriteEndElement();
                StreamSeeds.Add(propertyPath + "".{0}"", new KeyValuePair<PropertyInfo, object> (typeof({1}).GetProperty(""{0}""), value.{0}));
            }}
";

        private string SerializeSV(PropertyInfo property)
        {
            if (property.PropertyType == typeof(Stream))
                return string.Format(CSharpStreamTypeSerializeLiteral, property.Name, property.DeclaringType.FullName);
            if (ValueType(property.PropertyType))
                return SerializeSimple(property);
            return SerializeCompound(property);
        }

        private const string CSharpCompoundLiteral =
@"            if (value.{0} != null)
            {{
                xwrtr.WriteStartElement(""{0}"");
                {1}.Serialize(value.{0}, null, xwrtr, StreamSeeds, propertyPath + "".{0}"", serializerWrapper, doVersioning);
                xwrtr.WriteEndElement();{3}
            }}{2}
";

        private const string CSharpScalableLiteral =
@"            if (value.{0} != null)
            {{
                xwrtr.WriteStartElement(""{0}"");
                {1}.Serialize(value.{0}, null, xwrtr, StreamSeeds, propertyPath + "".{0}"", serializerWrapper, doVersioning);
                xwrtr.WriteEndElement();
                value.{0}.Flush (serializerWrapper); 
            }}
";

        private string SerializeCompound(PropertyInfo property)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };
            if (actualType.HostTypeName == "CIP4ReferenceType")
            {
                return string.Format(CSharpReferenceTypeSerializeLiteral,
                    property.Name);
            }
            if (actualType.HostTypeName == "CIP4ScalablePropertyType")
            {
                foreach (TypeContextType tc in (TargetTypes(actualType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpScalableLiteral,
                    property.Name,
                    actualType.TypeSerializerName);
            }
            if (IncludeThisType(property.PropertyType))
            {
                foreach (TypeContextType tc in (TargetTypes(actualType)))
                    UsedTypes.Add(tc.TheTypeFullName, tc);
                return string.Format(CSharpCompoundLiteral,
                    property.Name,
                    actualType.TypeSerializerName,
                    SerializeCompoundVersions(property),
                    CompoundVersionsWasNull(property));
            }
            return string.Empty;
        }

        // {0} - qualified name of property owner
        // {1} - property name
        // {2} - serializer name for property type
        // {3} - property owner version property name
        private const string CSharpSerializeCompoundVersionsLiteral =
            @"
            else if (value.{3} != null && (({0})value.{3}.GetCurrent<{0}>()).{1} != null)
            {{
                value.Versions.AddValue(""{1}"", AddressType_Serializer.SerializeToXElement(
                    (({0})value.{3}.GetCurrent<{0}>()).{1}, serializerWrapper));
            }}
";

        private string SerializeCompoundVersions(PropertyInfo property)
        {
            TypeContextType propertyType = new TypeContextType(property.PropertyType);

            if (VersionProperty == null || propertyType.VersionProperty == null)
                return string.Empty;

            return string.Format(CSharpSerializeCompoundVersionsLiteral,
                TheType.TheTypeFullName,
                property.Name,
                propertyType.TypeSerializerName,
                VersionProperty.Name);
        }

        // {0} - qualified name of property owner
        // {1} - property owner version property name
        // {2} - property name
        private const string CSharpCompoundVersionsWasNullLiteral =
@"
                if (value.{1} != null && (({0})value.{1}.GetCurrent<{0}>()).{2} == null)
                    value.{1}.AddValue(""{2}"", new XElement(""{2}""));  // does this make sense?
";

        private string CompoundVersionsWasNull(PropertyInfo property)
        {
            TypeContextType propertyType = new TypeContextType(property.PropertyType);

            if (VersionProperty == null || propertyType.VersionProperty == null)
                return string.Empty;

            return string.Format(CSharpCompoundVersionsWasNullLiteral,
                TheType.TheTypeFullName,
                VersionProperty.Name,
                property.Name);
        }

        private const string CSharpReferenceTypeSerializeLiteral =
@"            if (value.{0} != null && value.{0}.HasValue)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteStartElement(""CIP4ReferenceType"");
                xwrtr.WriteStartElement(""TargetExternalID"");
                xwrtr.WriteElementString(""String"", ((CIP4ReferenceBaseType)value.{0}).TargetExternalID);
                xwrtr.WriteEndElement();  // TargetExternalID
                xwrtr.WriteEndElement();  // CIP4ReferenceType
                xwrtr.WriteEndElement();  // property name
            }}
";

        private const string CSharpValueTypeSerializeLiteral =
@"            if (value.{0} != default({3}))
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteElementString(""{1}"", {2});
                xwrtr.WriteEndElement();{4}
            }}{5}
";

        private const string CSharpNullableValueTypeSerializeLiteral =
@"            if (value.{0}.HasValue)
            {{
                xwrtr.WriteStartElement(""{0}"");
                xwrtr.WriteElementString(""{1}"", {2});
                xwrtr.WriteEndElement();{3}
            }}{4}
";

        private string SerializeSimple(PropertyInfo property)
        {
            TypeContextType actualType = new TypeContextType { OriginalType = property.PropertyType };
            if (actualType.HostTypeName == "CIP4ReferenceType")
                return string.Format(CSharpReferenceTypeSerializeLiteral,
                    property.Name);
            if (ValueType(actualType.TheType))
            {
                if (!IncludeThisProperty(property))
                    return string.Empty;
                if (actualType.HostTypeName == "Nullable")
                    return string.Format(CSharpNullableValueTypeSerializeLiteral,
                        property.Name,
                        actualType.TheTypeName,
                        SerializeSimpleValue(property),
                        SerializeSimpleVersions(property),
                        SerializeSVNullVersion(property)) + Environment.NewLine;
                return string.Format(CSharpValueTypeSerializeLiteral,
                    property.Name,
                    actualType.TheTypeName,
                    SerializeSimpleValue(property),
                    actualType.TheTypeFullName,
                    SerializeSimpleVersions(property),
                    SerializeSVNullVersion(property)) + Environment.NewLine;
            }
            return string.Empty;
        }

        private string SerializeSimpleMVValue(PropertyInfo property, string valueName)
        {
            Type actualType = GetActualType(property.PropertyType);
            TypeContextType contextType = new TypeContextType { OriginalType = property.PropertyType };
            string valueString = contextType.HostTypeName == "Nullable" ? ".Value" : string.Empty;
            if (actualType == typeof(string))
                return valueName;
            if (actualType == typeof(char))
                return string.Format("string.Format(\"0x{{0:X}}\", System.Convert.ToInt16({0}))", valueName + valueString);
            if (actualType.IsEnum)
                return string.Format("{0}.ToString()", valueName + valueString);
            if (actualType.Name == "Color")
                return string.Format("RgbForColor({0})", valueName + valueString);
            if (actualType == typeof(DateTime))
                return string.Format("XmlConvert.ToString({0}, Globals.DateTimeSerializationMode)", valueName + valueString);
            if (actualType == typeof(TimeSpan))
                return string.Format("XmlConvert.ToString({0})", valueName + valueString);
            return string.Format("{0}.ToString()", valueName);

        }

        // this should agree with XmlValueFromObject in SerializerBaseType
        private static string SerializeSimpleValue(PropertyInfo property)
        {
            Type actualType = GetActualType(property.PropertyType);
            TypeContextType contextType = new TypeContextType { OriginalType = property.PropertyType };
            string valueString =  contextType.HostTypeName == "Nullable"? ".Value" : string.Empty;
            if (actualType  == typeof(string))
                return string.Format("value.{0}", property.Name);
            if (actualType == typeof(char))
                return string.Format("string.Format(\"0x{{0:X}}\", System.Convert.ToInt16(value.{0}))", property.Name + valueString);
            if (actualType == typeof(Single) || actualType == typeof(Double))
                return string.Format("DoubleConverter.ToExactString(value.{0})", property.Name + valueString);
            if (actualType.IsEnum)
                return string.Format("value.{0}.ToString()", property.Name + valueString);
            if (actualType.Name == "Color")
                return string.Format("RgbForColor(value.{0})", property.Name + valueString);
            if (actualType == typeof(DateTime))
                return string.Format("XmlConvert.ToString(value.{0}, Globals.DateTimeSerializationMode)", property.Name + valueString);
            if (actualType == typeof(TimeSpan))
                return string.Format("XmlConvert.ToString(value.{0})", property.Name + valueString);
            return string.Format("value.{0}.ToString()", property.Name);

        }
#endregion
    }
}
